﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using System.Threading;

namespace COC.Graphics
{
	enum EChildLoc
	{
		TopLeft = 0,
		TopRight = 1,
		BottomLeft = 2,
		BottomRight = 3,
	}

	enum ENeighborLoc
	{
		Left = 0,
		Top = 1,
		Right = 2,
		Bottom = 3,
	}

	class TerrainManager : IDisposable
	{
		#region .ctor

		static TerrainManager _SingletonInstance = null;
		public static TerrainManager Singleton
		{
			get
			{
				if(_SingletonInstance == null)
				{
					_SingletonInstance = new TerrainManager();
				}
				return _SingletonInstance;
			}
		}


		TerrainManager()
		{

		}

		#endregion

		#region Public Methods

		/// <summary>
		/// 初始化地形管理器
		/// </summary>
		/// <param name="sizeu">u方向上尺寸(世界坐標)</param>
		/// <param name="sizev">v方向上尺寸(世界坐標)</param>
		/// <param name="maxLevel">最大細分等級</param>
		/// <param name="tileDimentions">地形貼塊的頂點維度</param>
		/// <param name="heightMapDimentions">高度圖維度</param>
		/// <param name="heightMap">高度圖文件</param>
		/// <param name="textureRoot">紋理存儲位置</param>
		public void Initialize(
			float sizeu, float sizev, int maxLevel,
			int tileDimentions, int heightMapDimentions,
			string heightMap, string textureRoot)
		{
			if (m_bInitialized)
				return;

			m_TerrainSystemSize = new Vector2(sizeu, sizev);
			m_MaxLevel = maxLevel;
			m_TileDimensions = tileDimentions;
			m_HeightMapDimensions = heightMapDimentions;

			m_TileVertexCount = tileDimentions * tileDimentions;
			m_TileTriangleCount = (tileDimentions - 1) * (tileDimentions - 1) * 2;

			m_IndexBuffers = new IndexBuffer[9];
			m_Fix2LevelIndexBuffers = new IndexBuffer[4];
			m_BaseTexture = new Texture2D[1];

			LoadHeightmap(heightMap, heightMapDimentions);
			CreateIndexBuffers();

			m_TextureRoot = textureRoot;
			TextureCreationParameters tcparam = new TextureCreationParameters(
				0, 0, 0, 0, SurfaceFormat.Unknown, TextureUsage.None,
				Color.White, FilterOptions.Dither | FilterOptions.Triangle, FilterOptions.Box);
			m_BaseTexture[0] = Texture2D.FromFile(CoCGame.Singleton.GraphicsDevice, textureRoot, tcparam);

			m_bInitialized = true;

			m_RootTile = new TerrainTile(0, 0, null, EChildLoc.TopLeft);
			m_RootTile.SetAllNeighbors(new TerrainTile[] { null, null, null, null });

			ThreadStart ts = new ThreadStart(UpdateTerrainThreadProc);
			m_UpdateTerrainThread = new Thread(ts);
			m_UpdateTerrainThread.Start();
		}

		private void UpdateTerrainThreadProc()
		{
			while (true)
			{
				if (CoCGame.Singleton.GameCamera != null)
				{
					m_CameraUV = GetUVByPosition(CoCGame.Singleton.GameCamera.Position);
					m_RootTile.Update();
				}
				else
				{
					break;
				}
				Thread.Sleep(1000);
			}
		}

		internal Vector2 GetUVByPosition(Vector3 pos)
		{
			float u, v;
			u = pos.X / m_TerrainSystemSize.X + 0.5f;
			v = pos.Z / m_TerrainSystemSize.Y + 0.5f;
			return new Vector2(u, v);
		}

		internal Vector3 GetPositionByUV(Vector2 uv)
		{
			return new Vector3(
				(uv.X - 0.5f) * m_TerrainSystemSize.X,
				TerrainManager.Singleton.SampleHeightMap(uv.X, uv.Y),
				(uv.Y - 0.5f) * m_TerrainSystemSize.Y);
		}

		public void Dispose()
		{
			if (!m_bInitialized)
				return;

			m_UpdateTerrainThread.Abort();

			foreach (Texture2D baseTextures in m_BaseTexture)
			{
				baseTextures.Dispose();
			}
			m_BaseTexture = null;

			foreach (IndexBuffer ibs in m_IndexBuffers)
			{
				ibs.Dispose();
			}
			m_IndexBuffers = null;

			//foreach (IndexBuffer f2ibs in m_Fix2LevelIndexBuffers)
			//{
			//    f2ibs.Dispose();
			//}
			//m_Fix2LevelIndexBuffers = null;

			m_HeightMapData = null;

			_SingletonInstance = null;
			m_bInitialized = true;
		}

		public float SampleHeightMap(Vector3 worldCoord)
		{
			Vector2 uv = GetUVByPosition(worldCoord);
			return SampleHeightMap(uv.X, uv.Y);
		}

		public float SampleHeightMap(float u, float v)
		{
			if (!m_bInitialized || m_HeightMapData == null)
				return 0.0f;

			if (u > 1 || v > 1 || u < 0 || v < 0)
			{
				return 0.0f;
			}

			float fracX = u * (m_HeightMapDimensions - 1);
			float fracZ = v * (m_HeightMapDimensions - 1);
			int x0 = (int)Math.Floor(fracX);
			int x1 = x0 + 1;
			int z0 = (int)Math.Floor(fracZ);
			int z1 = z0 + 1;
			if (x0 < 0)
				x0 = 0;
			if (x1 > m_HeightMapDimensions - 1)
				x1 = m_HeightMapDimensions - 1;
			if (z0 < 0)
				z0 = 0;
			if (z1 > m_HeightMapDimensions - 1)
				z1 = m_HeightMapDimensions - 1;

			fracX = fracX - x0;
			fracZ = fracZ - z0;

			float f00 = m_HeightMapData[x0 * m_HeightMapDimensions + z0];
			float f01 = m_HeightMapData[x0 * m_HeightMapDimensions + z1];
			float f10 = m_HeightMapData[x1 * m_HeightMapDimensions + z0];
			float f11 = m_HeightMapData[x1 * m_HeightMapDimensions + z1];

			return f00 * (1 - fracX) * (1 - fracZ)
				+ f10 * fracX * (1 - fracZ)
				+ f01 * (1 - fracX) * fracZ
				+ f11 * fracX * fracZ;
		}

		#endregion

		#region Private Methods
		void LoadHeightmap(string heightMapFn, int dimentions)
		{
			m_HeightMapData = new float[dimentions * dimentions];
			try
			{
				FileStream fs = File.Open(heightMapFn, FileMode.Open);
				BinaryReader br = new BinaryReader(fs);

				float heightRange = m_MinMaxHeight.Y - m_MinMaxHeight.X;
				for(int i=0; i<m_HeightMapData.Length; i++)
				{
					ushort data = br.ReadUInt16();
					m_HeightMapData[i] = (float)(data) / 0xffff * heightRange + m_MinMaxHeight.X;
				}
			}
			catch
			{
			}
		}

		void CreateIndexBuffers()
		{
			List<short[]> TempIndexData = new List<short[]>();

			for (int i = 0; i < 9; i++)
			{
				short[] indexArray = new short[m_TileTriangleCount * 3];
				TempIndexData.Add(indexArray);
			}

			int k = 0;

			for (int i = 0; i < m_TileDimensions - 1; i++)
			{
				bool triangleFlag = ((i % 2) == 0);
				for (int j = 0; j < m_TileDimensions - 1; j++)
				{
					short a, b, c, d;
					a = (short)(i * m_TileDimensions + j);
					b = (short)((i + 1) * m_TileDimensions + j);
					c = (short)((i + 1) * m_TileDimensions + j + 1);
					d = (short)(i * m_TileDimensions + j + 1);

					for (int arrIndex = 0; arrIndex < 9; arrIndex++)
					{
						short tempa = a, tempb = b, tempc = c, tempd = d;
						ModifyIndex(arrIndex, i, j, ref tempa, ref tempb, ref tempc, ref tempd);

						TempIndexData[arrIndex][k] = tempa;
						TempIndexData[arrIndex][k + 1] = tempb;
						if (triangleFlag)
						{
							TempIndexData[arrIndex][k + 2] = tempc;
							TempIndexData[arrIndex][k + 3] = tempa;
							TempIndexData[arrIndex][k + 4] = tempc;
						}
						else
						{
							TempIndexData[arrIndex][k + 2] = tempd;
							TempIndexData[arrIndex][k + 3] = tempb;
							TempIndexData[arrIndex][k + 4] = tempc;
						}
						TempIndexData[arrIndex][k + 5] = tempd;
					}

					k += 6;
					triangleFlag = !triangleFlag;
				}
			}

			for (int i = 0; i < 9; i++)
			{
				m_IndexBuffers[i] = new IndexBuffer(CoCGame.Singleton.GraphicsDevice,
					sizeof(short) * m_TileTriangleCount * 3, BufferUsage.None, IndexElementSize.SixteenBits);
				m_IndexBuffers[i].SetData<short>(TempIndexData[i]);
			}

			m_BaseIndicesData = TempIndexData[0];
		}

		void ModifyIndex(int arrayIndex, int i, int j,
			ref short a, ref short b, ref short c, ref short d)
		{
			if (arrayIndex == 0)
				return;

			ushort fIMax = 0x01, fIMin = 0x02, fJMax = 0x04, fJMin = 0x08;
			ushort[] flags = new ushort[] {0x01, 0x08, 0x02, 0x04, 0x09, 0x0a, 0x06, 0x05 };
			ushort flag = flags[arrayIndex - 1];
			if((flag & fIMax) != 0)
			{
				if(i==m_TileDimensions - 2)
				{
					if(0 == (j & 1))
						c += 1;
					else
						b += 1;
				}
			}
			if((flag & fIMin) != 0)
			{
				if (i == 0)
				{
					if (0 == (j & 1))
						d += 1;
					else
						a += 1;
				}
			}
			if((flag & fJMax) != 0)
			{
				if (j == m_TileDimensions - 2)
				{
					if (0 == (i & 1))
						c += (short)m_TileDimensions;
					else
						d += (short)m_TileDimensions;
				}
			}
			if((flag & fJMin) != 0)
			{
				if (j == 0)
				{
					if (0 == (i & 1))
						b += (short)m_TileDimensions;
					else
						a += (short)m_TileDimensions;
				}
			}
		}
		#endregion

		#region Var

		bool m_bInitialized = false;

		int m_HeightMapDimensions;
		string m_TextureRoot;

		internal int m_TileDimensions;
		internal int m_MaxLevel;
		internal float[] m_HeightMapData;

		internal short[] m_BaseIndicesData;
		
		internal int m_TileVertexCount;
		internal int m_TileTriangleCount;

		internal Vector2 m_MinMaxHeight = new Vector2(-50.0f, 150.0f);

		internal Vector2 m_TerrainSystemSize;
		internal Vector2 m_CameraUV;

		internal IndexBuffer[] m_IndexBuffers;
		internal IndexBuffer[] m_Fix2LevelIndexBuffers;
		internal Texture2D[] m_BaseTexture;

		internal Effect CurrentEffect = null;

		TerrainTile m_RootTile;
		public TerrainTile RootTile
		{
			get { return m_RootTile; }
		}

		Thread m_UpdateTerrainThread;

		#endregion
	}

	class TerrainTile : ICullable, IRenderable, IDisposable
	{
		public TerrainTile(float u, float v,
			TerrainTile parent, EChildLoc loc)
		{
			m_bHasChild = false;
			m_u = u;
			m_v = v;
			m_location = loc;

			m_Parent = parent;

			if (m_Parent != null)
			{
				m_level = parent.m_level + 1;
			}
			else
			{
				m_level = 0;
			}

			m_UVSize = 1.0f / (1 << m_level);

			float halfUVSize = m_UVSize * 0.5f;

			m_center_u = m_u + halfUVSize;
			m_center_v = m_v + halfUVSize;

			m_leftEx_u = m_center_u - m_UVSize;
			m_rightEx_u = m_center_u + m_UVSize;
			m_topEx_v = m_center_v - m_UVSize;
			m_bottomEx_v = m_center_v + m_UVSize;

			m_exUs[0] = m_leftEx_u;
			m_exUs[2] = m_rightEx_u;
			m_exUs[1] = m_exUs[3] = m_center_u;

			m_exVs[1] = m_topEx_v;
			m_exVs[3] = m_bottomEx_v;
			m_exVs[0] = m_exVs[2] = m_center_v;


			float xMinPos = (m_u - 0.5f) * TerrainManager.Singleton.m_TerrainSystemSize.X;
			float zMinPos = (m_v - 0.5f) * TerrainManager.Singleton.m_TerrainSystemSize.Y;

			float xMaxPos = (m_u + m_UVSize - 0.5f) * TerrainManager.Singleton.m_TerrainSystemSize.X;
			float zMaxPos = (m_v + m_UVSize - 0.5f) * TerrainManager.Singleton.m_TerrainSystemSize.Y;

			m_BoudingBox = new BoundingBox(
				new Vector3(xMinPos, TerrainManager.Singleton.m_MinMaxHeight.X, zMinPos),
				new Vector3(xMaxPos, TerrainManager.Singleton.m_MinMaxHeight.Y, zMaxPos));

			BuildTileVertexBuffers();
		}

		public void RenderDepth()
		{

		}

		public void Render()
		{
			if (!m_BoudingBox.Intersects(CoCGame.Singleton.GameCamera.Frustum))
			{
				return;
			}

			if (m_bHasChild)
			{
				lock (m_Children.SyncRoot)
				{
					for (int i = 0; i < 4; i++)
					{
						m_Children[i].Render();
					}
				}
				return;
			}

			GraphicsDevice device = CoCGame.Singleton.GraphicsDevice;

			device.Vertices[0].SetSource(m_PositionStream, 0, sizeof(float) * 3);
			device.Vertices[1].SetSource(m_NormalStream, 0, sizeof(float) * 3);
			device.Vertices[2].SetSource(m_TextureCoordStream, 0, sizeof(float) * 2);

			int ibIndex, gapLevel2;
			ChooseIndexBuffer(out ibIndex, out gapLevel2);

			device.Indices = TerrainManager.Singleton.m_IndexBuffers[ibIndex];
			device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
				TerrainManager.Singleton.m_TileVertexCount, 0, TerrainManager.Singleton.m_TileTriangleCount);
		}

		public void Update()
		{
			Vector2 cUV = TerrainManager.Singleton.m_CameraUV;
			float distanceToCenter = (new Vector2(cUV.X - m_center_u, cUV.Y - m_center_v)).Length();
			float testFactor = (float)Math.Atan2(m_UVSize / 16, distanceToCenter);

			if (testFactor > 3.14f / 70 && m_level < TerrainManager.Singleton.m_MaxLevel)
			{
				if(!m_bHasChild)
				{
					Split();
				}
			}

			else if (testFactor < 3.14f / 80)
			{
				if(m_bHasChild)
				{
					m_bHasChild = false;
					lock (m_Children.SyncRoot)
					{
						for (int i = 0; i < 4; i++)
						{
							m_Children[i].Dispose();
							m_Children[i] = null;
						}
					}
				}
			}

			if(m_bHasChild)
			{
				for(int i=0; i<4; i++)
				{
					m_Children[i].Update();
				}
			}
		}

		public void Dispose()
		{
			m_PositionStream.Dispose();
			m_TextureCoordStream.Dispose();
			m_NormalStream.Dispose();
		}

		void ChooseIndexBuffer(out int iBIndex, out int gapLevel2)
		{
			iBIndex = 0;
			gapLevel2 = 0;

			TerrainTile currentTile = null;
			int indexFlag = 0;
			for (int i = 0; i < 4; i++)
			{
				if (m_Neighbor[i] == null)
				{
					currentTile = null;
				}
				else
				{
					currentTile = m_Neighbor[i].GetTileAtUVAndLevel(m_exUs[i], m_exVs[i], m_level);
				}

				if (currentTile != null)
				{
					if (currentTile.m_level < m_level)
					{
						indexFlag |= (1 << i);
						if (currentTile.m_level < m_level - 1)
						{
							gapLevel2 = i;
						}
					}
				}
			}

			int arrIndex = 0;
			switch (indexFlag)
			{
				case 2: arrIndex = 2; break;	//left
				case 1: arrIndex = 3; break;	//top
				case 8: arrIndex = 4; break;	//right
				case 4: arrIndex = 1; break;	//bottom
				case 3: arrIndex = 6; break;	//left top
				case 9: arrIndex = 7; break;	//top right
				case 12: arrIndex = 8; break;	//right bottom
				case 6: arrIndex = 5; break;	//left bottom
			}

			iBIndex = arrIndex;
		}

		internal void SetAllNeighbors(TerrainTile[] LeftRightTopBottom)
		{
			m_Neighbor[0] = LeftRightTopBottom[0];
			m_Neighbor[1] = LeftRightTopBottom[2];
			m_Neighbor[2] = LeftRightTopBottom[1];
			m_Neighbor[3] = LeftRightTopBottom[3];
		}

		void BuildTileVertexBuffers()
		{
			float DeltaUVPerGird = m_UVSize / (TerrainManager.Singleton.m_TileDimensions - 1);

			int vertCount = TerrainManager.Singleton.m_TileVertexCount;
			Vector3[] PositionData = new Vector3[vertCount];
			Vector3[] NormalData = new Vector3[vertCount];
			Vector2[] TexcoordData = new Vector2[vertCount];

			int PositionNormalDataByteCount = sizeof(float) * 3 * vertCount;
			int TexcoordDataByteCount = sizeof(float) * 2 * vertCount;

			short[] IndicesData = TerrainManager.Singleton.m_BaseIndicesData;

			Vector2 terrainSystemSize = TerrainManager.Singleton.m_TerrainSystemSize;

			float minY = float.MaxValue, maxY = float.MinValue;

			for (int i = 0; i < TerrainManager.Singleton.m_TileDimensions; i++)
			{
				float xPos = (m_u + i * DeltaUVPerGird - 0.5f) * terrainSystemSize.X;

				for (int j = 0; j < TerrainManager.Singleton.m_TileDimensions; j++)
				{
					float zPos = (m_v + j * DeltaUVPerGird - 0.5f) * terrainSystemSize.Y;
					float yPos = TerrainManager.Singleton.SampleHeightMap(m_u + i * DeltaUVPerGird, m_v + j * DeltaUVPerGird);
					
					minY = minY > yPos ? yPos : minY;
					maxY = maxY < yPos ? yPos : maxY;

					int index = i * TerrainManager.Singleton.m_TileDimensions + j;
					PositionData[index] = new Vector3(xPos, yPos, zPos);
					TexcoordData[index] = new Vector2(m_u + i * DeltaUVPerGird, m_v + j * DeltaUVPerGird);
				}
			}

			m_BoudingBox.Min.Y = minY;
			m_BoudingBox.Max.Y = maxY;

			int indexCount = TerrainManager.Singleton.m_TileTriangleCount * 3;
			int[] addCounter = new int[vertCount];

			for (int i = 0; i < indexCount; i += 3)
			{
				Vector3 v12 = PositionData[IndicesData[i + 1]] - PositionData[IndicesData[i]];
				Vector3 v13 = PositionData[IndicesData[i + 2]] - PositionData[IndicesData[i + 1]];
				Vector3 faceNormal = Vector3.Normalize(Vector3.Cross(v12, v13));
				if (faceNormal.Y < 0)
				{
					faceNormal = -faceNormal;
				}
				for (int k = i; k < i + 3; k++)
				{
					NormalData[IndicesData[k]] += faceNormal;
					addCounter[IndicesData[k]]++;
				}
			}

			for (int i = 0; i < vertCount; i++)
			{
				NormalData[i] /= (float)addCounter[i];
			}

			m_PositionStream = new VertexBuffer(CoCGame.Singleton.GraphicsDevice, PositionNormalDataByteCount, BufferUsage.None);
			m_NormalStream = new VertexBuffer(CoCGame.Singleton.GraphicsDevice, PositionNormalDataByteCount, BufferUsage.None);
			m_TextureCoordStream = new VertexBuffer(CoCGame.Singleton.GraphicsDevice, TexcoordDataByteCount, BufferUsage.None);

			m_PositionStream.SetData<Vector3>(PositionData);
			m_NormalStream.SetData<Vector3>(NormalData);
			m_TextureCoordStream.SetData<Vector2>(TexcoordData);
		}

		TerrainTile GetTileAtUV(float u, float v)
		{
			if (!(u >= m_u && u < m_u + m_UVSize && v >= m_v && v < m_v + m_UVSize))
			{
				return null;
			}
			if (!m_bHasChild)
			{
				return this;
			}
			int flagRight = (u < m_center_u) ? 0 : 1;
			int flagBottom = (v < m_center_v) ? 0 : 2;
			int index = flagRight | flagBottom;
			return m_Children[index].GetTileAtUV(u, v);
		}

		TerrainTile GetTileAtUVAndLevel(float u, float v, int level)
		{
			if (!(u >= m_u && u < m_u + m_UVSize && v >= m_v && v < m_v + m_UVSize))
			{
				return null;
			}

			if (m_level >= level)
			{
				return this;
			}

			int flagRight = (u < m_center_u) ? 0 : 1;
			int flagBottom = (v < m_center_v) ? 0 : 2;

			int index = flagRight | flagBottom;

			lock (m_Children.SyncRoot)
			{
				if (!m_bHasChild)
				{
					return this;
				}
				return m_Children[index].GetTileAtUVAndLevel(u, v, level);
			}
		}

		void Split()
		{
			if (m_level == TerrainManager.Singleton.m_MaxLevel)
			{
				return;
			}

			lock (m_Children.SyncRoot)
			{
				m_Children[(int)EChildLoc.TopLeft] = new TerrainTile(
					m_u, m_v, this, EChildLoc.TopLeft);
				m_Children[(int)EChildLoc.TopRight] = new TerrainTile(
					m_center_u, m_v, this, EChildLoc.TopRight);
				m_Children[(int)EChildLoc.BottomLeft] = new TerrainTile(
					m_u, m_center_v, this, EChildLoc.BottomLeft);
				m_Children[(int)EChildLoc.BottomRight] = new TerrainTile(
					m_center_u, m_center_v, this, EChildLoc.BottomRight);

				m_Children[(int)EChildLoc.TopLeft].SetAllNeighbors(new TerrainTile[]{
						m_Neighbor[(int)ENeighborLoc.Left],
						m_Children[(int)EChildLoc.TopRight],
						m_Neighbor[(int)ENeighborLoc.Top],
						m_Children[(int)EChildLoc.BottomLeft],});
				m_Children[(int)EChildLoc.TopRight].SetAllNeighbors(new TerrainTile[]{
						m_Children[(int)EChildLoc.TopLeft],
						m_Neighbor[(int)ENeighborLoc.Right],
						m_Neighbor[(int)ENeighborLoc.Top],
						m_Children[(int)EChildLoc.BottomRight]});
				m_Children[(int)EChildLoc.BottomLeft].SetAllNeighbors(new TerrainTile[]{
						m_Neighbor[(int)ENeighborLoc.Left],
						m_Children[(int)EChildLoc.BottomRight],
						m_Children[(int)EChildLoc.TopLeft],
						m_Neighbor[(int)ENeighborLoc.Bottom],});
				m_Children[(int)EChildLoc.BottomRight].SetAllNeighbors(new TerrainTile[]{
						m_Children[(int)EChildLoc.BottomLeft],
						m_Neighbor[(int)ENeighborLoc.Right],
						m_Children[(int)EChildLoc.TopRight],
						m_Neighbor[(int)ENeighborLoc.Bottom],});

				m_bHasChild = true;
			}
		}

		#region Var
		EChildLoc m_location;
		TerrainTile[] m_Children = new TerrainTile[4];
		TerrainTile m_Parent;
		TerrainTile[] m_Neighbor = new TerrainTile[4];

		float m_u;
		float m_v;

		float m_leftEx_u;
		float m_rightEx_u;
		float m_topEx_v;
		float m_bottomEx_v;

		float m_center_u;
		float m_center_v;

		float[] m_exUs = new float[4];
		float[] m_exVs = new float[4];

		float m_UVSize;
		int m_level;

		volatile bool m_bHasChild = false;

		VertexBuffer m_PositionStream;
		VertexBuffer m_TextureCoordStream;
		VertexBuffer m_NormalStream;
		#endregion
	}
}